﻿using System.Collections.Generic;
using System;
using pbvs.Models.DomainModels;
using pbvs.Models.ViewModels;
using System.Configuration;
using pbvs.Data;
using System.Web;
using pbvs.Models;
using System.Linq;

namespace pbvs.Calculations
{
    public class Valuation
    {
		public ValuationRequestDomainModel Request { get; set; }
        public int FirstNYears = 20;
        public decimal Interest20Year
        {
            get
            {
                return Request.InterestRate / 100;
            }
        }

        public decimal InterestThereafter
        {
            get
            {
                return Request.InterestRateThereafter / 100;
            }
        }

		public IList<CommutationTable> CtVars { get; set; }
	    private decimal _min1;
        private decimal _min2;
        private decimal _min3;
        private decimal _max1;
        public decimal TotalBenefitService { get; set; }
        public decimal MarriageService { get; set; }
        public decimal DeferredAndTemporaryLifeAnnuity { get; set; }
        public decimal LifeAnnuityNax { get; set; }
        public decimal LastSurvivorAnnuity { get; set; }
        public decimal LastSurvivorAnnuityNYears { get; set; }

        public decimal Cola
        {
            get
            {
                return Request.Cola / 100;
            }
        }

        public decimal LifeAnnuityGx1
        {
            get
            {
                return GetComplexVariable(this.AgeN, CommutationTable.Rate.First20, CommutationTable.Calculation.Scale2022);
            }
        }

        public decimal LifeAnnuityGx2
        {
            get
            {
                return GetComplexVariable(this.AttainedAge, CommutationTable.Rate.First20, CommutationTable.Calculation.Scale2022);
            }
        }

        public decimal AdjustedAx
        {
            get
            {
                return (LifeAnnuityNax * LifeAnnuityGx1) / LifeAnnuityGx2;
            }
        }

        public decimal FinalLifeAnnuityFactor
        {
            get
            {
                return DeferredAndTemporaryLifeAnnuity + AdjustedAx;
            }
        }

        public decimal PresentValueAtProjectedDate
        {
            get
            {
                return FinalLifeAnnuityFactor * Request.BenefitAtRetirement * 12;
            }
        }

        public decimal MaritalPortionOfPresentValue
        {
            get
            {
                return PresentValueAtProjectedDate * CovertureFraction;
            }
        }

        public decimal ShortTermDeferredEndowment
        {
            get
            {
                return
                    GetComplexVariable(this.RetirementAge, CommutationTable.Rate.First20,
                                       CommutationTable.Calculation.Scale2022) /
                    GetComplexVariable(this.AttainedAge, CommutationTable.Rate.First20,
                                       CommutationTable.Calculation.Scale2022);
            }
        }

        public decimal LongTermDeferredEndowment
        {
            get
            {
                return
                    GetComplexVariable(this.AgeN, CommutationTable.Rate.First20, CommutationTable.Calculation.Scale2022) /
                    GetComplexVariable(this.AttainedAge, CommutationTable.Rate.First20,
                                       CommutationTable.Calculation.Scale2022);
            }
        }

        public decimal LongTermDeferredEndowment2
        {
            get
            {
                return
                    GetComplexVariable(this.RetirementAge, CommutationTable.Rate.Thereafter,
                                       CommutationTable.Calculation.Scale2022) /
                    GetComplexVariable(this.AgeN, CommutationTable.Rate.Thereafter,
                                       CommutationTable.Calculation.Scale2022);
            }
        }

        public bool UseShortTermFactor
        {
            get
            {
                return (this.RetirementAge - this.AttainedAge) < this.FirstNYears;
            }
        }

        public decimal AttainedAge
        {
            get
            {
                return CalculateAge(Request.Dob, Request.ValuationDate);
            }
        }

        public int AgeInYears
        {
            get
            {
                return Convert.ToInt32(Math.Round(AttainedAge, 0));
            }
        }

        public decimal AgeN
        {
            get
            {
                return AttainedAge + FirstNYears;
            }
        }

        public decimal SurvivorAttainedAge
        {
            get
            {
                return CalculateAge(Request.SurvivorDob, Request.ValuationDate);
            }
        }

        public int SurvivorAttainedAgeInYears
        {
            get
            {
                return Convert.ToInt32(Math.Round(SurvivorAttainedAge, 0));
            }
        }

        public decimal SurvivorAgeN
        {
            get
            {
                return SurvivorAttainedAge + FirstNYears;
            }
        }


        public decimal RetirementAge
        {
            get
            {
                return CalculateAge(Request.RetirementDate, Request.Dob);
            }
        }

        public int RetirementAgeInYears
        {
            get
            {
                return Convert.ToInt32(Math.Round(RetirementAge, 0));
            }
        }

        public decimal BeneficiaryAge
        {
            get
            {
                return this.SurvivorAttainedAge + (this.RetirementAge - this.AttainedAge);
            }
        }

        public int BeneficiaryAgeInYears
        {
            get
            {
                return Convert.ToInt32(Math.Round(BeneficiaryAge, 0));
            }
        }

        public decimal CovertureFraction
        {
            get
            {
                return Request.UseFractional ? (MarriageService / TotalBenefitService) < 1 ? MarriageService / TotalBenefitService : 1 : Request.MaritalPortion / 100;
            }
        }

        public CommutationTable.Gender Gender
        {
            get
            {
                return Request.Gender == "M" ? CommutationTable.Gender.Male : CommutationTable.Gender.Female;
            }
        }

        public CommutationTable.Gender SurvivorGender
        {
            get
            {
                return Request.SurvivorGender == "M" ? CommutationTable.Gender.Male : CommutationTable.Gender.Female;
            }
        }

        public List<int> JointSurvivor1
        {
            get
            {
                List<int> list = new List<int>();
                int endAge = Convert.ToInt32(RetirementAge) + 120;
                for (int x = Convert.ToInt32(RetirementAge); x <= endAge; x++)
                {
                    list.Add(x);
                }
                return list;
            }
        }

        public List<int> JointSurvivor2
        {
            get
            {
                List<int> list = new List<int>();
                int endAge = Convert.ToInt32(BeneficiaryAge) + 120;
                for (int x = Convert.ToInt32(BeneficiaryAge); x <= endAge; x++)
                {
                    list.Add(x);
                }
                return list;
            }
        }

        public decimal ColaAdjustment
        {
            get
            {
                return this.FirstNYears + this.AttainedAge - this.RetirementAge > 0
                           ? Convert.ToDecimal(Math.Pow(Convert.ToDouble(1 + this.Cola),
                                                        Convert.ToDouble(this.FirstNYears + this.AttainedAge -
                                                                         this.RetirementAge)))
                           : 1;
            }
        }

        public decimal InterestAdjustment
        {
            get
            {
                return
                    Convert.ToDecimal(Math.Pow(
                        Convert.ToDouble((1 + this.InterestThereafter) / (1 + this.Interest20Year)), 20));
            }
        }

        public decimal Case1Factor
        {
            get
            {
                return ShortTermDeferredEndowment*LastSurvivorAnnuity +
                       LastSurvivorAnnuityNYears*ColaAdjustment*InterestAdjustment;
            }
        }

        public decimal Case2Factor
        {
            get { return CalculateCase2Factor(); }
        }

        public decimal JointSurvivorFinalFactor
        {
            get { return UseShortTermFactor ? Case1Factor : Case2Factor; }
        }

        public decimal SurvivorshipPortionOfPresentValue
        {
            get { return Request.BenefitAtRetirement*12*JointSurvivorFinalFactor - PresentValueAtProjectedDate; }
        }

        public Valuation()
        {
            //TODO: Get Rid of this after debugging
        }

        public
                Valuation(VariablesFull vars)
            : this(new ValuationRequestDomainModel(), vars) { }

        public Valuation(ValuationRequestDomainModel request, VariablesFull vars)
        {
            Request = request;
	        CtVars = vars.BuildVariableList(this.Interest20Year, this.InterestThereafter, this.Cola);
            SetValuationVariables();
        }

        private void SetValuationVariables()
        {
            CalculateTbs();
            CalculateMarriageService();
            CalculateMin1Variable();
            CalculateMin2Variable();
            CalculateMin3Variable();
            CalculateMax1Variable();
            CalculateDtlaf();
            CalculateLafnf();
	        if (Request.Survivorship)
	        {
		        CalculateLsan();
		        CalculateNLsa();
	        }
        }

        protected void CalculateTbs()
        {
            TimeSpan ts = (Request.Dot - Request.Doh);
            TotalBenefitService = Convert.ToDecimal(ts.TotalDays / 365.25);
        }

        protected decimal CalculateAge(DateTime dt1, DateTime dt2)
        {
            DateTime firstDate = dt1;
            DateTime secondDate = dt2;
            if (firstDate > secondDate)
            {
                firstDate = dt2;
                secondDate = dt1;
            }

            int day1 = firstDate.DayOfYear;
            int day2 = secondDate.DayOfYear;
            decimal dateDiff = Convert.ToDecimal(Math.Abs(day1 - day2));
            decimal daysInYear = Convert.ToDecimal(new DateTime(secondDate.Year, 12, 31).DayOfYear);
            if (DateTime.IsLeapYear(secondDate.Year))
            {
                dateDiff--;
            }
            if (DateTime.IsLeapYear(firstDate.Year))
            {
                dateDiff++;
            }
            decimal yearFrac = dateDiff / daysInYear;

            return (secondDate.Year - firstDate.Year) + yearFrac;
        }

        public static decimal FractionOfYear(decimal age)
        {
            return age - Math.Floor(age);
        }

        public decimal InterestRate(string rate)
        {
            return (String.IsNullOrEmpty(rate) || rate != "T") ? Interest20Year : InterestThereafter;
        }

        protected void CalculateMarriageService()
        {
            TimeSpan ts = Request.Dot - Request.MarriageDate;
            MarriageService = Convert.ToDecimal(ts.TotalDays / 365.25);
        }

        protected void CalculateDtlaf()
        {
            CommutationTable ct = new CommutationTable(this.AgeInYears, this.Gender, this.CtVars, this.Cola);
            decimal dx = GetComplexVariable(this.AttainedAge, CommutationTable.Rate.First20, CommutationTable.Calculation.Scale2022);
            DeferredAndTemporaryLifeAnnuity = (this._min1 - Convert.ToDecimal(Math.Pow((1 + Convert.ToDouble(this.Cola)), Convert.ToDouble(this._max1))) * this._min2) / dx;
        }

        protected void CalculateMin1Variable()
        {
            decimal min1Aa = GetComplexVariable(this.AttainedAge, CommutationTable.Rate.First20, CommutationTable.Calculation.Ax);
            decimal min1Ra = GetComplexVariable(this.RetirementAge, CommutationTable.Rate.First20, CommutationTable.Calculation.Ax);
            _min1 = min1Aa < min1Ra ? min1Aa : min1Ra;
        }

        protected void CalculateMin2Variable()
        {
            decimal min1Aa = GetComplexVariable(this.AgeN, CommutationTable.Rate.First20, CommutationTable.Calculation.Ax);
            decimal min1Ra = GetComplexVariable(this.RetirementAge, CommutationTable.Rate.First20, CommutationTable.Calculation.Ax);
            _min2 = min1Aa < min1Ra ? min1Aa : min1Ra;
        }

        private void CalculateMax1Variable()
        {
            _max1 = this.FirstNYears - (this.RetirementAge - this.AttainedAge) > 0 ? this.FirstNYears - (this.RetirementAge - this.AttainedAge) : 0;
        }

        private void CalculateMin3Variable()
        {
            decimal min1Aa = GetComplexVariable(this.AgeN, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Ax);
            decimal min1Ra = GetComplexVariable(this.RetirementAge, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Ax);
            _min3 = min1Aa < min1Ra ? min1Aa : min1Ra;
        }

        protected decimal GetComplexVariable(decimal age, CommutationTable.Rate rate, CommutationTable.Calculation calc, bool useSurvivor = false)
        {
            CommutationTable ct = new CommutationTable((int)Math.Floor(age) + 1, useSurvivor ? this.SurvivorGender : this.Gender, this.CtVars, this.Cola);
            decimal cfAgePlusOne = ct.GetVariable(rate, calc);
            ct = new CommutationTable((int)Math.Floor(age), useSurvivor ? this.SurvivorGender : this.Gender, this.CtVars, this.Cola);
            decimal cfAge = ct.GetVariable(rate, calc);
            decimal vf = 0m;
            if (calc == CommutationTable.Calculation.Scale2022)
            {
                vf = rate == CommutationTable.Rate.First20 ? (decimal)Math.Pow((double)(1 + Interest20Year), (double)-(age)) : (decimal)Math.Pow((double)(1 + InterestThereafter), (double)-(age));
            }
            else
            {
                vf = GetComplexVariable(age, rate, CommutationTable.Calculation.Scale2022, useSurvivor);
            }
            return (FractionOfYear(age) * cfAgePlusOne + (1 - FractionOfYear(age)) * cfAge) * vf;
        }

        protected void CalculateLafnf()
        {

            decimal cv = GetComplexVariable(this.AgeN, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Scale2022);
            this.LifeAnnuityNax = (this._min3 / cv) * (decimal)Math.Pow((double)(1 + this.Cola), (double)this._max1);
        }

        protected void CalculateLsan()
        {
            decimal beneficiaryNormalRa = this.RetirementAge + this.SurvivorAttainedAge - this.AttainedAge;
            bool equalAges = JointSurvivor1[0] - JointSurvivor2[0] == Math.Floor(this.AgeN) - Math.Floor(this.SurvivorAgeN);
            decimal ageNDivideRetirementAx =
                GetComplexVariable(this.AgeN, CommutationTable.Rate.First20, CommutationTable.Calculation.Ax) /
                GetComplexVariable(this.RetirementAge, CommutationTable.Rate.First20,
                                   CommutationTable.Calculation.Scale2022);
            decimal survivorDivideBeneficiaryAx =
                GetComplexVariable(this.SurvivorAgeN, CommutationTable.Rate.First20,
                                   CommutationTable.Calculation.Ax, true) /
                GetComplexVariable(this.BeneficiaryAge, CommutationTable.Rate.First20,
                                   CommutationTable.Calculation.Scale2022, true);
            decimal ageNDivideRetirement =
                GetComplexVariable(this.AgeN, CommutationTable.Rate.First20, CommutationTable.Calculation.Scale2022) /
                GetComplexVariable(this.RetirementAge, CommutationTable.Rate.First20,
                                   CommutationTable.Calculation.Scale2022);
            decimal survivorDivideBeneficiary =
                GetComplexVariable(this.SurvivorAgeN, CommutationTable.Rate.First20,
                                       CommutationTable.Calculation.Scale2022, true) /
                    GetComplexVariable(this.BeneficiaryAge, CommutationTable.Rate.First20,
                                       CommutationTable.Calculation.Scale2022, true);
            decimal onePlusI20 = Convert.ToDecimal(Math.Pow(1 + Convert.ToDouble(this.Interest20Year),
                                               Convert.ToDouble(this.FirstNYears + this.AttainedAge - this.RetirementAge)));
            decimal colaCalc = Convert.ToDecimal(Math.Pow(1 + Convert.ToDouble(this.Cola),
                                               Convert.ToDouble(this.FirstNYears + this.AttainedAge - this.RetirementAge)));
            LastSurvivorAnnuity lsan = new LastSurvivorAnnuity(JointSurvivor1, JointSurvivor2, this.Gender,
                                                               this.SurvivorGender, this.Interest20Year, this.Cola, this.RetirementAge, this.BeneficiaryAge, true, CtVars)
                                           {
                                               AttainedAge = this.AttainedAge,
                                               SurvivorAge = this.SurvivorAttainedAge,
                                               SurvivorPercentage = this.Request.SurvivorshipPercentage / 100,
                                               BeneficiaryNormalRa = beneficiaryNormalRa,
                                               FirstNYears = this.FirstNYears,
                                               EqualAges = equalAges
                                           };

            LastSurvivorAnnuity = lsan.CalculateLsan(ageNDivideRetirementAx, survivorDivideBeneficiaryAx, ageNDivideRetirement,
                               survivorDivideBeneficiary, onePlusI20, colaCalc);
        }

        protected void CalculateNLsa()
        {
            decimal beneficiaryNormalRa = this.RetirementAge + this.SurvivorAttainedAge - this.AttainedAge;
            bool equalAges = JointSurvivor1[0] - JointSurvivor2[0] == Math.Floor(this.AgeN) - Math.Floor(this.SurvivorAgeN);
            decimal ageNAxDivideRetirement =
                GetComplexVariable(this.AgeN, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Ax)/
                GetComplexVariable(this.RetirementAge, CommutationTable.Rate.Thereafter,
                                   CommutationTable.Calculation.Scale2022);
            decimal survivorAxDivideBeneficiary =
                GetComplexVariable(this.SurvivorAgeN, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Ax, true)/
                GetComplexVariable(this.BeneficiaryAge, CommutationTable.Rate.Thereafter,
                                   CommutationTable.Calculation.Scale2022, true);
            decimal ageNDivideRetirement =
                GetComplexVariable(this.AgeN, CommutationTable.Rate.Thereafter, CommutationTable.Calculation.Scale2022)/
                GetComplexVariable(this.RetirementAge, CommutationTable.Rate.Thereafter,
                                   CommutationTable.Calculation.Scale2022);
            decimal survivorDivideBeneficiary =
                GetComplexVariable(this.SurvivorAgeN, CommutationTable.Rate.Thereafter,
                                   CommutationTable.Calculation.Scale2022, true)/
                GetComplexVariable(this.BeneficiaryAge, CommutationTable.Rate.Thereafter,
                                   CommutationTable.Calculation.Scale2022, true);
            LastSurvivorAnnuity lsan = new LastSurvivorAnnuity(JointSurvivor1, JointSurvivor2, this.Gender,
                                                               this.SurvivorGender, this.InterestThereafter,
                                                               this.Cola, this.RetirementAge,
                                                               this.BeneficiaryAge, false, CtVars)
                                           {
                                               AttainedAge = this.AttainedAge,
                                               SurvivorAge = this.SurvivorAttainedAge,
                                               SurvivorPercentage = this.Request.SurvivorshipPercentage / 100,
                                               BeneficiaryNormalRa = beneficiaryNormalRa,
                                               FirstNYears = this.FirstNYears,
                                               EqualAges = equalAges
                                           };
            this.LastSurvivorAnnuityNYears = lsan.CalculateNLsa(ageNAxDivideRetirement, survivorAxDivideBeneficiary,
                                                                ageNDivideRetirement, survivorDivideBeneficiary);
        }

        protected decimal CalculateCase2Factor()
        {
            decimal beneficiaryNormalRa = this.RetirementAge + this.SurvivorAttainedAge - this.AttainedAge;
            bool equalAges = JointSurvivor1[0] - JointSurvivor2[0] == Math.Floor(this.AgeN) - Math.Floor(this.SurvivorAgeN);
            LastSurvivorAnnuity lsan = new LastSurvivorAnnuity(JointSurvivor1, JointSurvivor2, this.Gender,
                                                               this.SurvivorGender, this.InterestThereafter,
                                                               this.Cola, this.RetirementAge,
                                                               this.BeneficiaryAge, false, CtVars)
                                           {
                                               AttainedAge = this.AttainedAge,
                                               SurvivorAge = this.SurvivorAttainedAge,
                                               SurvivorPercentage = this.Request.SurvivorshipPercentage / 100,
                                               BeneficiaryNormalRa = beneficiaryNormalRa,
                                               FirstNYears = this.FirstNYears,
                                               EqualAges = equalAges
                                           };
            return lsan.CalculateCase2Factor(this.LongTermDeferredEndowment, this.LongTermDeferredEndowment2);
        }
    }
}